package com.ljm.security.conf;

import com.ljm.security.api.SecurityApi;
import com.ljm.security.core.CustomAuthenticationEntryPoint;
import com.ljm.security.core.CustomAuthenticationProvider;
import com.ljm.security.core.CustomRoleVoter;
import com.ljm.security.filter.HandleCurUserFilter;
import com.ljm.security.filter.PreLoginFilter;
import com.ljm.security.handler.CustomAccessDeniedHandler;
import com.ljm.security.handler.CustomAuthenticationFailureHandler;
import com.ljm.security.handler.CustomAuthenticationSuccessHandler;
import com.ljm.security.handler.CustomLogoutSuccessHandler;
import com.ljm.security.loginprocessor.FormLoginPostProcessor;
import com.ljm.security.loginprocessor.JsonLoginPostProcessor;
import com.ljm.security.loginprocessor.LoginPostProcessor;
import org.springframework.boot.autoconfigure.session.SessionAutoConfiguration;
import org.springframework.boot.autoconfigure.session.SessionProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.web.servlet.server.Session;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.vote.AuthenticatedVoter;
import org.springframework.security.access.vote.UnanimousBased;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.expression.WebExpressionVoter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.session.security.web.authentication.SpringSessionRememberMeServices;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
import org.springframework.util.ClassUtils;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @author: ChenHuaMing
 * @Date: 2020/6/10 16:55
 * @Description:
 */
@EnableConfigurationProperties({ ServerProperties.class})
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter {
    private static final String REMEMBER_ME_SERVICES_CLASS = "org.springframework.security.web.authentication.RememberMeServices";
    @Resource
    private CustomAuthenticationProvider authenticationProvider;
    @Resource
    private CustomAccessDeniedHandler accessDeniedHandler;
    @Resource
    private CustomAuthenticationEntryPoint authenticationEntryPoint;
    @Resource
    private CustomAuthenticationSuccessHandler authenticationSuccessHandler;
    @Resource
    private CustomAuthenticationFailureHandler authenticationFailureHandler;
    @Resource
    private CustomLogoutSuccessHandler logoutSuccessHandler;
    @Resource
    @Lazy
    private SecurityApi securityApi;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(authenticationProvider);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/doLogin").permitAll()
                .antMatchers("/favicon.ico").permitAll()
                .accessDecisionManager(accessDecisionManager())
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginProcessingUrl("/doLogin")
                .failureHandler(authenticationFailureHandler)
                .successHandler(authenticationSuccessHandler)
                .and()
                .logout().logoutUrl("/logout")
                .logoutSuccessHandler(logoutSuccessHandler)
                .and()
                .exceptionHandling().accessDeniedHandler(accessDeniedHandler)
                .authenticationEntryPoint(authenticationEntryPoint)
                .and()
                .cors()
                .and()
                .addFilterBefore(preLoginFilter(), UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(new HandleCurUserFilter(), UsernamePasswordAuthenticationFilter.class)
                .csrf().disable();

    }

    @Bean
    @Primary
    public PasswordEncoder passwordEncoder(){
        return  new BCryptPasswordEncoder();
    }

    public PreLoginFilter preLoginFilter(){
        Set<LoginPostProcessor> set=new HashSet<>();
        set.add(new FormLoginPostProcessor());
        set.add(new JsonLoginPostProcessor());
        PreLoginFilter preLoginFilter=new PreLoginFilter("/doLogin",set);
        return preLoginFilter;
    }

    @Bean
    public AccessDecisionManager accessDecisionManager() {
        List<AccessDecisionVoter<? extends Object>> decisionVoters
                = Arrays.asList(
                new WebExpressionVoter(),
                // new RoleVoter(),
                new CustomRoleVoter(securityApi),
                new AuthenticatedVoter());
        return new UnanimousBased(decisionVoters);
    }

    @Bean
    public CookieSerializer httpSessionIdResolver(ServerProperties serverProperties){
        Session.Cookie cookie = serverProperties.getServlet().getSession().getCookie();
        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
        PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
        map.from(cookie::getName).to(cookieSerializer::setCookieName);
        map.from(cookie::getDomain).to(cookieSerializer::setDomainName);
        map.from(cookie::getPath).to(cookieSerializer::setCookiePath);
        map.from(cookie::getHttpOnly).to(cookieSerializer::setUseHttpOnlyCookie);
        map.from(cookie::getSecure).to(cookieSerializer::setUseSecureCookie);
        map.from(cookie::getMaxAge).to((maxAge) -> cookieSerializer.setCookieMaxAge((int) maxAge.getSeconds()));
        if (ClassUtils.isPresent(REMEMBER_ME_SERVICES_CLASS, getClass().getClassLoader())) {
            new RememberMeServicesCookieSerializerCustomizer().apply(cookieSerializer);
        }
        cookieSerializer.setSameSite(null);
        return cookieSerializer;
    }

    /**
     * Customization for {@link SpringSessionRememberMeServices} that is only instantiated
     * when Spring Security is on the classpath.
     */
    static class RememberMeServicesCookieSerializerCustomizer {
        void apply(DefaultCookieSerializer cookieSerializer) {
            cookieSerializer.setRememberMeRequestAttribute(SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR);
        }
    }
}
